﻿using System;
using System.Collections.Generic;
using System.Text;
using OAuth;
using System.Net;
using System.Web;
using System.IO;
using System.Diagnostics;
using System.Threading;
using Butterfly.SNS;

namespace Butterfly.OAuth
{
    public class ApiAdresses
    {
        public Uri RequestTokenUrl;
        public Uri AccessTokenUrl;
        public Uri AuthorizationUrl;
        public Uri StatusUrl;
    }

    public enum RequestType
    {
        Unknown = 0,
        Post,
        Delete,
        Get
    }

    public enum Method { GET, POST, PUT, DELETE };

    public enum RequestCategory
    {
        Unknown = 0,
        Status,         // Micro Blog.
        Message,
        Friend
    }

    public class AuthEventArgs : EventArgs
    {
        public bool IsAuthorized;
    }

    // Should be singleton
    public abstract class OAuthBase
    {
        #region Fields
        public string accessToken;
        public string accessTokenSecret;
        private string requestToken;
        private string requestTokenSecret;
        private ApiAdresses apiAdresses;
        #endregion

        #region Constructors

        public OAuthBase(ApiAdresses addresses)
        {
            this.apiAdresses = addresses;
        }

        public OAuthBase(ApiAdresses addresses, string token, string tokenSecret)
            : this(addresses)
        {
            this.accessToken = token;
            this.accessTokenSecret = tokenSecret;
        }

        #endregion

        #region Properties

        protected abstract string ApiKey { get; }
        protected abstract string ApiKeySecret { get; }

        public delegate void AuthEventHandler(object sender, AuthEventArgs e);

        public event AuthEventHandler OnAuthorizationChanged;

        public bool IsAuthorized
        {
            get
            {
                return !(String.IsNullOrEmpty(accessToken) || String.IsNullOrEmpty(accessTokenSecret));
            }
        }

        #endregion

        #region private Methods

        private Dictionary<string, string> parseResponse(string parameters)
        {
            Dictionary<string, string> result = new Dictionary<string, string>();

            if (!string.IsNullOrEmpty(parameters))
            {
                string[] p = parameters.Split('&');
                foreach (string s in p)
                    if (!string.IsNullOrEmpty(s))
                        if (s.IndexOf('=') > -1)
                        {
                            string[] temp = s.Split('=');
                            result.Add(temp[0], temp[1]);
                        }
                        else result.Add(s, string.Empty);
            }

            return result;
        }

        // 1. get request token. 
        //      This step use API Key and API Key Secret to sign.
        private void getRequestToken()
        {
            if (IsAuthorized)
                return;

            Uri uri = apiAdresses.RequestTokenUrl;
            string nonce = OAuthBasic.GenerateNonce();
            string timeStamp = OAuthBasic.GenerateTimeStamp();
            string normalizeUrl, normalizedRequestParameters;

            // signature
            string sig = OAuthBasic.GenerateSignature(
                uri,
                ApiKey,
                ApiKeySecret,
                string.Empty,
                string.Empty,
                "GET",
                timeStamp,
                nonce,
                OAuthBasic.SignatureTypes.HMACSHA1,
                out normalizeUrl,
                out normalizedRequestParameters);
            sig = HttpUtility.UrlEncode(sig);

            //构造请求Request Token的url
            StringBuilder sb = new StringBuilder(uri.ToString());
            sb.AppendFormat("?oauth_consumer_key={0}&", ApiKey);
            sb.AppendFormat("oauth_nonce={0}&", nonce);
            sb.AppendFormat("oauth_timestamp={0}&", timeStamp);
            sb.AppendFormat("oauth_signature_method={0}&", "HMAC-SHA1");
            sb.AppendFormat("oauth_version={0}&", "1.0");
            sb.AppendFormat("oauth_signature={0}", sig);

            Console.WriteLine("请求Request Token的url: \n" + sb.ToString());

            //请求Request Token
            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(sb.ToString());
            HttpWebResponse response = (HttpWebResponse)request.GetResponse();
            StreamReader stream = new StreamReader(response.GetResponseStream(), System.Text.Encoding.UTF8);
            string responseBody = stream.ReadToEnd();
            stream.Close();
            response.Close();

            // parse returned content to get Request Token and Request Token Secret
            Dictionary<string, string> responseValues = parseResponse(responseBody);
            requestToken = responseValues["oauth_token"];
            requestTokenSecret = responseValues["oauth_token_secret"];
        }

        // 2. User verify certification
        private void authorization()
        {
            string url = apiAdresses.AuthorizationUrl + "?oauth_token=" + requestToken;

            System.Diagnostics.Process.Start(url);
        }

        // 3. 换取Access Token，该步骤使用API Key、API Key Secret、Request Token和Request Token Secret签名
        public void GetAccessToken(string verifier)
        {
            Uri uri = apiAdresses.AccessTokenUrl;
            string nonce = OAuthBasic.GenerateNonce();
            string timeStamp = OAuthBasic.GenerateTimeStamp();
            string normalizeUrl, normalizedRequestParameters;

            // Sign
            string sig = OAuthBasic.GenerateSignature(
                uri,
                ApiKey,
                ApiKeySecret,
                requestToken,
                requestTokenSecret,
                "GET",
                timeStamp,
                nonce,
                OAuthBasic.SignatureTypes.HMACSHA1,
                verifier,
                out normalizeUrl,
                out normalizedRequestParameters);
            sig = HttpUtility.UrlEncode(sig);

            // Build url for request Access Token
            StringBuilder sb = new StringBuilder(uri.ToString());
            sb.AppendFormat("?oauth_consumer_key={0}", ApiKey);
            sb.AppendFormat("&oauth_nonce={0}", nonce);
            sb.AppendFormat("&oauth_timestamp={0}", timeStamp);
            sb.AppendFormat("&oauth_signature_method={0}", "HMAC-SHA1");
            sb.AppendFormat("&oauth_version={0}", "1.0");
            sb.AppendFormat("&oauth_signature={0}", sig);
            sb.AppendFormat("&oauth_token={0}", requestToken);
            if (!String.IsNullOrEmpty(verifier))
            {
                sb.AppendFormat("&oauth_verifier={0}", verifier);
            }

            // Request Access Token
            HttpWebRequest request = WebRequest.Create(sb.ToString()) as HttpWebRequest;
            request.Headers.Add("Authorization", "OAuth realm=\"\"");
            try
            {
                HttpWebResponse response = request.GetResponse() as HttpWebResponse;
                StreamReader stream = new StreamReader(response.GetResponseStream(), System.Text.Encoding.UTF8);
                string responseBody = stream.ReadToEnd();
                stream.Close();
                response.Close();

                // Parse returned content to get Access Token and Access Token Secret.
                Dictionary<string, string> responseValues = parseResponse(responseBody);
                accessToken = responseValues["oauth_token"];
                accessTokenSecret = responseValues["oauth_token_secret"];
                Trace.TraceInformation("Getting Access Token Succeed!");
                if (OnAuthorizationChanged != null)
                    OnAuthorizationChanged(this, new AuthEventArgs() { IsAuthorized = true });
            }
            catch (WebException ex)
            {
                Trace.TraceError(ex.Message);
                Trace.TraceInformation("Certify failed, waiting 3 seconds then try again!");
                Thread.Sleep(3000);
            }


        }

        #endregion private Methods

        // 4. 访问受限资源，该步骤使用API Key、API Key Secret、Access Token和Access Token Secret签名
        #region Public Methods

        public string BuildAuthHeader(RequestCategory category, RequestType type)
        {
            string header = null;
            switch (category)
            {
                case RequestCategory.Status:
                    header = BuildStatusAuthHeader(type);
                    break;

                default:
                    break;
            }

            return header;
        }

        private string BuildStatusAuthHeader(RequestType type)
        {
            Uri uri = apiAdresses.StatusUrl;
            string nonce = OAuthBasic.GenerateNonce();
            string timeStamp = OAuthBasic.GenerateTimeStamp();
            string normalizeUrl, normalizedRequestParameters;

            string sig = OAuthBasic.GenerateSignature(
                uri,
                ApiKey,
                ApiKeySecret,
                accessToken,
                accessTokenSecret,
                "POST",
                timeStamp,
                nonce,
                OAuthBasic.SignatureTypes.HMACSHA1,
                out normalizeUrl,
                out normalizedRequestParameters);
            sig = HttpUtility.UrlEncode(sig);

            StringBuilder oauthHeader = new StringBuilder();
            oauthHeader.AppendFormat("OAuth realm=\"\", oauth_consumer_key={0}, ", ApiKey);
            oauthHeader.AppendFormat("oauth_nonce={0}, ", nonce);
            oauthHeader.AppendFormat("oauth_timestamp={0}, ", timeStamp);
            oauthHeader.AppendFormat("oauth_signature_method={0}, ", "HMAC-SHA1");
            oauthHeader.AppendFormat("oauth_version={0}, ", "1.0");
            oauthHeader.AppendFormat("oauth_signature={0}, ", sig);
            oauthHeader.AppendFormat("oauth_token={0}", accessToken);

            return oauthHeader.ToString();
        }


        #endregion


        public void StartAuth()
        {

            /* Fix HttpWebRequest vs. lighttpd bug
             * More details in http://www.gnegg.ch/2006/09/lighttpd-net-httpwebrequest/
             */
            System.Net.ServicePointManager.Expect100Continue = false;

            if (!IsAuthorized)
            {
                getRequestToken();
                authorization();
            }
        }

    }
}
